﻿#region License

// PentaBiz - Sustainable Software Development Framework Copyright (C) 2013 Zoltán Csizmazia
// 
// This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
// 
// This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 

#endregion

using System;
using System.Collections;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Ipc;
using System.Runtime.Serialization.Formatters;
using System.Security.AccessControl;
using System.Threading;
using System.Windows.Threading;

namespace PentaBiz.Studio.PluginHost
{
    internal class Program
    {
        internal static string Name { get; set; }

        internal static string PluginDirectory { get; set; }

        internal static string PluginAssemblyName { get; set; }
        internal static Assembly PluginAssembly { get; set; }


        internal static Dispatcher Dispatcher { get; private set; }

        [STAThread]
        [LoaderOptimization(LoaderOptimization.MultiDomainHost)]
        private static void Main(string[] args)
        {
            if (args.Length != 3)
            {
                Console.WriteLine("usage: PluginHost.exe Name PluginDirectory PluginAssemblyName");
            }

            int bits = IntPtr.Size*8;
            Console.WriteLine("Starting PluginHost {0}, {1} bit at {2}", Name, bits, PluginDirectory);

            //instead of doing AppDomain-ApplicationBase Re-basing DO copy and simple local execution from here...
            if (args.Length == 3)
            {
                Name = args[0];
                PluginDirectory = args[1];
                PluginAssemblyName = args[2];

                Console.WriteLine("Args: {0}, {1}, {2}", Name, PluginDirectory, PluginAssemblyName);


                string pluginHostLocation = Assembly.GetExecutingAssembly().Location;
                string pluginHostExe = Path.GetFileNameWithoutExtension(pluginHostLocation) + ".exe";
                string pluginHostPdb = Path.GetFileNameWithoutExtension(pluginHostLocation) + ".pdb";

                string destinationPath1 = Path.Combine(Path.GetFullPath(PluginDirectory), pluginHostExe);
                //if (!File.Exists(destinationPath1))
                File.Copy(Path.GetFullPath(pluginHostExe), destinationPath1, true);
                string destinationPath2 = Path.Combine(Path.GetFullPath(PluginDirectory), pluginHostPdb);
                //if (!File.Exists(destinationPath2))
                File.Copy(Path.GetFullPath(pluginHostPdb), destinationPath2, true);

                // cache path = directory where the assemblies get
                // (shadow) copied:
                //string cachePath = Path.Combine(startupPath, "__cache");
                //string configFile = pluginHostLocation + ".config";

                // Create the setup for the new domain:
                var setup = new AppDomainSetup();
                setup.ApplicationName = pluginHostExe;
                //setup.ShadowCopyDirectories = "";
                //setup.ApplicationBase = Path.GetFullPath(PluginDirectory);
                //setup.PrivateBinPathProbe = null;
                setup.PrivateBinPath = Path.GetFullPath(PluginDirectory);
                //setup.ShadowCopyFiles = "true"; // note: it isn't a bool
                //setup.CachePath = cachePath;
                //setup.ConfigurationFile = configFile;

                // Create the application domain. The evidence of this
                // running assembly is used for the new domain:
                AppDomain domain = AppDomain.CreateDomain(pluginHostExe,
                                                          null, //AppDomain.CurrentDomain.Evidence,
                                                          setup);


                // Start MyApplication by executing the assembly:
                domain.ExecuteAssembly(Path.GetFullPath(Path.Combine(PluginDirectory, pluginHostExe)),
                                       new[] {Name, PluginDirectory, PluginAssemblyName, "copied"});

                // After the MyApplication has finished clean up:
                AppDomain.Unload(domain);
            }
            else if (args.Length == 4)
            {
                Name = args[0];
                PluginDirectory = args[1];
                PluginAssemblyName = args[2];

                Console.WriteLine("Args: {0}, {1}, {2}", Name, PluginDirectory, PluginAssemblyName);

                PluginAssembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a =>
                                                                                        a.GetName().Name ==
                                                                                        PluginAssemblyName);
                if (PluginAssembly == null)
                {
                    var pluginPath = Path.Combine(PluginDirectory, PluginAssemblyName + ".dll");
                    PluginAssembly = Assembly.LoadFile(Path.GetFullPath(pluginPath));

                    PluginHost.PluginAssembly = PluginAssembly;
                }
                Run();
            }
        }


        public static void Run()
        {
            var pluginDirectoryFull = Path.GetFullPath(PluginDirectory);

            AppDomain.CurrentDomain.DomainUnload += CurrentDomainOnDomainUnload;


            try
            {
                Dispatcher = Dispatcher.CurrentDispatcher;

                var serverProvider = new BinaryServerFormatterSinkProvider {TypeFilterLevel = TypeFilterLevel.Full};
                var clientProvider = new BinaryClientFormatterSinkProvider();
                var properties = new Hashtable();
                properties["portName"] = Name;

                var channel = new IpcChannel(properties, clientProvider, serverProvider);
                ChannelServices.RegisterChannel(channel, false);

                /*RemotingConfiguration.RegisterWellKnownServiceType(
                    typeof(PluginHost), "PluginLoader", WellKnownObjectMode.Singleton);*/

                //The call to RemotingServices::Marshal registers an instance of a type, not a type. 
                //This means that the registered instance has the same behavior as a singleton object. 
                var pluginHostService = new PluginHost(PluginDirectory, PluginAssemblyName);
                RemotingServices.Marshal(pluginHostService, "PluginLoader");

                pluginHostService.GetPluginCommandTypeSelectors();
                pluginHostService.GetPluginControlTypeSelectors();
                pluginHostService.GetPluginServiceTypeSelectors();

                SignalEvent(Name, "Ready");

                Console.WriteLine("Dispatcher.Run()...");
                Dispatcher.Run();

                Console.WriteLine("Dispatcher is closed.");
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine(ex);
                File.AppendAllText("PentaBiz.Studio.PluginLoader.Error.txt", ex.ToString());
                SignalEvent(Name, "Error");
            }
            finally
            {
                SignalEvent(Name, "Terminate");
            }
        }

        private static void CurrentDomainOnDomainUnload(object sender, EventArgs eventArgs)
        {
            if (!Dispatcher.HasShutdownStarted)
            {
                Dispatcher.InvokeShutdown();
            }
            SignalEvent(Name, "Exit");
        }

        private static void SignalEvent(string name, string @event)
        {
            var eventName = name + "." + @event;
            EventWaitHandle waitHandle;
            if (EventWaitHandle.TryOpenExisting(eventName, EventWaitHandleRights.Modify, out waitHandle))
            {
                Console.WriteLine("Signalling {0}", eventName);
                waitHandle.Set();
            }
        }
    }
}